// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#include "table/format.h"

#include "leveldb/env.h"
#include "port/port.h"
#include "table/block.h"
#include "table/bloom_filter.h"
#include "util/coding.h"
#include "util/crc32c.h"

namespace leveldb {

void BlockHandle::EncodeTo(std::string* dst) const {
  // Sanity check that all fields have been set
  assert(offset_ != ~static_cast<uint64_t>(0));
  assert(size_ != ~static_cast<uint64_t>(0));
  PutVarint64(dst, offset_);
  PutVarint64(dst, size_);
}

Status BlockHandle::DecodeFrom(Slice* input) {
  if (GetVarint64(input, &offset_) &&
      GetVarint64(input, &size_)) {
    return Status::OK();
  } else {
    return Status::Corruption("bad block handle");
  }
}

void Footer::EncodeTo(std::string* dst) const {
#ifndef NDEBUG
  const size_t original_size = dst->size();
#endif
  // [bloomfilter] [metaindex] [index]
  size_t handles_length = 2 * BlockHandle::kMaxEncodedLength;
  if (bloomfilter_) {
	bloomfilter_handle_.EncodeTo(dst);
	handles_length += BlockHandle::kMaxEncodedLength;
  }
  metaindex_handle_.EncodeTo(dst);
  index_handle_.EncodeTo(dst);
  dst->resize(handles_length);  // Padding
  PutFixed32(dst, static_cast<uint32_t>(kTableMagicNumber & 0xffffffffu));
  PutFixed32(dst, static_cast<uint32_t>(kTableMagicNumber >> 32));
  assert(dst->size() == original_size + kEncodedLength);
}

Status Footer::DecodeFrom(Slice* input) {
  const char* magic_ptr = input->data() + kEncodedLength - 8;
  const uint32_t magic_lo = DecodeFixed32(magic_ptr);
  const uint32_t magic_hi = DecodeFixed32(magic_ptr + 4);
  const uint64_t magic = ((static_cast<uint64_t>(magic_hi) << 32) |
                          (static_cast<uint64_t>(magic_lo)));
  if (magic != kTableMagicNumber) {
    return Status::InvalidArgument("not an sstable (bad magic number)");
  }

  Status result = Status::OK() ;
  if (result.ok() && bloomfilter_) {
	result = bloomfilter_handle_.DecodeFrom(input);
  }
  if (result.ok()) {
  	result = metaindex_handle_.DecodeFrom(input);
  }
  if (result.ok()) {
    result = index_handle_.DecodeFrom(input);
  }
  if (result.ok()) {
    // We skip over any leftover data (just padding for now) in "input"
    const char* end = magic_ptr + 8;
    *input = Slice(end, input->data() + input->size() - end);
  }
  return result;
}

Status ReadContent(RandomAccessFile* file,
                 const ReadOptions& options,
                 const BlockHandle& handle,
								 char **buf, size_t& n) {
	assert(*buf == NULL);
	n = static_cast<size_t>(handle.size());
	*buf = new char[n + kBlockTrailerSize];
	
	Slice contents;
	Status s = file->Read(handle.offset(), n + kBlockTrailerSize, &contents, *buf);
	if (!s.ok()) {
		delete[] *buf;
		return s;
	}
	if (contents.size() != n + kBlockTrailerSize) {
		delete[] *buf;
		return Status::Corruption("truncated block read");
	}

  // Check the crc of the type and the block contents
	const char* data = contents.data();
	if (options.verify_checksums) {
    const uint32_t crc = crc32c::Unmask(DecodeFixed32(data + n + 1));
    const uint32_t actual = crc32c::Value(data, n + 1);
    if (actual != crc) {
      delete[] *buf;
      s = Status::Corruption("block checksum mismatch");
      return s;
    }
  }

	switch (data[n]) {
    case kNoCompression:
      if (data != *buf) {
        // File implementation gave us pointer to some other data.
        // Copy into buf[].
        memcpy(*buf, data, n + kBlockTrailerSize);
      }
      // Ok
      break;

    case kSnappyCompression: {
      size_t ulength = 0;
      if (!port::Snappy_GetUncompressedLength(data, n, &ulength)) {
        delete[] *buf;
        return Status::Corruption("corrupted compressed block contents");
      }
      char* ubuf = new char[ulength];
      if (!port::Snappy_Uncompress(data, n, ubuf)) {
        delete[] *buf;
        delete[] ubuf;
        return Status::Corruption("corrupted compressed block contents");
      }
      delete[] *buf;
      *buf = ubuf;
      n = ulength;
      break;
    }

    default:
      delete[] *buf;
      return Status::Corruption("bad block type");
  }

	return Status::OK();
}

Status ReadBloomFilter(RandomAccessFile* file,
                 const ReadOptions& options,
                 const BlockHandle& handle,
                 BloomFilter** bloomfilter) {
  *bloomfilter = NULL;

  // Read the block contents as well as the type/crc footer.
  // See table_builder.cc for the code that built this structure.
  size_t n = 0;
  char* buf = NULL; 

	Status s = ReadContent(file, options, handle, &buf, n);
	if (s.ok()) {
		// buf malloc space in ReadContents;
		*bloomfilter = new BloomFilter(buf, n);  // Block takes ownership of buf[]
	}
  return s;
}


Status ReadBlock(RandomAccessFile* file,
                 const ReadOptions& options,
                 const BlockHandle& handle,
                 Block** block) {
  *block = NULL;

  // Read the block contents as well as the type/crc footer.
  // See table_builder.cc for the code that built this structure.
  size_t n = 0;
  char* buf = NULL; 

	Status s = ReadContent(file, options, handle, &buf, n);
	if (s.ok()) {
		// buf malloc space in ReadContents;
		*block = new Block(buf, n);  // Block takes ownership of buf[]
	}
  return s;
}

}  // namespace leveldb
